package com.supreme.threadserver.service;

import com.supreme.threadserver.service.supplier.HelloSupplier;
import com.supreme.threadserver.threadpkg.SupplierTask;
import com.supreme.threadserver.utils.StupidUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.*;

@Service
public class CompletableFutureService {

    @Resource
    private ThreadPoolTaskExecutor supremeTaskExecutor;

    private static void showThreadName(String message) {
        String name = Thread.currentThread().getName();
        System.out.println("threadName = " + name + " message = [ " + message + " ]");
    }

    private void showCompletableFutureResult(CompletableFuture<String> completableFuture) {
        try {
            String result = completableFuture.get();
            System.out.println("result = " + result);
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("e.getMessage() = " + e.getMessage());
        }
    }

    public void supplierClassCompletableFuture() {
        CompletableFuture<HelloSupplier> completableFuture = CompletableFuture.supplyAsync(HelloSupplier::new);
        try {
            completableFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * supplyAsync -> thenApply -> thenApplyAsync -> handleAsync -> whenCompleteAsync -> get
     * @return
     */
    public String simpleCompletableFuture() {
        StupidUtils.showThreadName("simpleCompletableFuture");

        CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                showThreadName("supplyAsync");
                return "supplyAsync";
            }
        }, supremeTaskExecutor).thenApply(new Function<String, String>() {
            @Override
            public String apply(String s) {
                showThreadName(s + " thenApply");
                return s + " " + "thenApply";
            }
        }).thenApplyAsync(new Function<String, String>() {
            @Override
            public String apply(String s) {
                showThreadName(s + " thenApplyAsync");
                /*if (Objects.nonNull(s)) {
                    throw new RuntimeException("abc");
                }*/
                return s + " " + "thenApplyAsync";
            }
        }, supremeTaskExecutor).handleAsync(new BiFunction<String, Throwable, String>() {
            @Override
            public String apply(String s, Throwable throwable) {
                showThreadName(s + " handleAsync");
                if (throwable != null) {
                    System.out.println("handleAsync1 throwable.getMessage() = " + throwable.getMessage());
                }
                return s + " " + "handleAsync";
            }
        }, supremeTaskExecutor).handleAsync(new BiFunction<String, Throwable, String>() {
            @Override
            public String apply(String s, Throwable throwable) {
                showThreadName(s + " handleAsync");
                if (throwable != null) {
                    System.out.println("handleAsync2 throwable.getMessage() = " + throwable.getMessage());
                }
                return s + " " + "handleAsync";
            }
        }, supremeTaskExecutor).whenCompleteAsync(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                showThreadName(s + " whenCompleteAsync");
                System.out.println("whenCompleteAsync throwable.getMessage() = " + throwable.getMessage());
            }
        });

        System.out.println("stringCompletableFuture FINISH");

        try {
            String result = stringCompletableFuture.get();
            System.out.println("result = " + result);
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("e.getMessage() = " + e.getMessage());
        }

        return "success";
    }

    /**
     * supplyAsync -> thenCombineAsync -> thenApplyAsync -> whenCompleteAsync
     */
    public void testCombine() {
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(new SupplierTask("task1"));
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(new SupplierTask("task2"));

        task1.thenCombineAsync(task2, (s, s2) -> {
            StupidUtils.showThreadName(s, s2, "thenCombineAsync");
            return String.format("%s %s thenCombineAsync", s, s2);
        }).thenApply(s -> {
            StupidUtils.showThreadName(s, "thenApplyAsync");
            return String.format("%s thenApplyAsync", s);
        /*}).whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "whenCompleteAsync");
                System.out.println("whenCompleteAsync throwable.getMessage() = " + throwable.getMessage());
            }*/
        }).handle(new BiFunction<String, Throwable, String>() {
            @Override
            public String apply(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "handle");
                Optional.ofNullable(throwable.getMessage()).ifPresent(message -> System.out.println("handle throwable.getMessage() = " + message));
                return String.format("%s handle", s);
            }
        });

        showCompletableFutureResult(task1);
        System.out.println("COMPLETE");
    }

    /**
     * supplyAsync -> thenAcceptBothAsync -> whenCompleteAsync
     */
    public void testAcceptBoth() {
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(new SupplierTask("task3"));
        CompletableFuture<String> task4 = CompletableFuture.supplyAsync(new SupplierTask("task4"));
        task3.thenAcceptBothAsync(task4, new BiConsumer<String, String>() {
            @Override
            public void accept(String s, String s2) {
                StupidUtils.showThreadName(s, s2, "thenAcceptBothAsync");
            }
        }).handle(new BiFunction<Void, Throwable, Object>() {
            @Override
            public Object apply(Void unused, Throwable throwable) {
                StupidUtils.showThreadName("handle");
                return "handle";
            }
        });

        showCompletableFutureResult(task3);
        System.out.println("COMPLETE");
    }

    /**
     * supplyAsync -> runAfterBothAsync -> runAfterBothAsync -> whenCompleteAsync
     */
    public void testRunAfterBoth() {
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(new SupplierTask("task1").setTimeout(1000L));
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(new SupplierTask("task2"));
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(new SupplierTask("task3"));
        task1.runAfterBothAsync(task2, new Runnable() {
            @Override
            public void run() {
                StupidUtils.showThreadName("runAfterBothAsync task2");
            }
        }).runAfterBothAsync(task3, new Runnable() {
            @Override
            public void run() {
                StupidUtils.showThreadName("runAfterBothAsync task3");
            }
        }).handleAsync(new BiFunction<Void, Throwable, String>() {
            @Override
            public String apply(Void unused, Throwable throwable) {
                StupidUtils.showThreadName("handleAsync");
                return "handleAsync";
            }
        }).whenCompleteAsync(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "whenCompleteAsync");
            }
        });

        showCompletableFutureResult(task1);
        System.out.println("COMPLETE");
    }

    /**
     * supplyAsync -> applyToEitherAsync -> whenComplete
     */
    public void testApplyToEither() {
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(new SupplierTask("task1").setTimeout(100L));
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(new SupplierTask("task2"));

        task1.applyToEitherAsync(task2, new Function<String, String>() {
            @Override
            public String apply(String s) {
                StupidUtils.showThreadName(s, "applyToEitherAsync");
                return "applyToEitherAsync";
            }
        }).whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "whenCompleteAsync");
            }
        });

//        showCompletableFutureResult(task1);
        System.out.println("COMPLETE");
    }

    /**
     * supplyAsync -> allOf -> thenApplyAsync -> handle -> whenComplete
     */
    public void testAllOf() {
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(new SupplierTask("task1"));
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(new SupplierTask("task2").setTimeout(1000L));
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(new SupplierTask("task3"));
        CompletableFuture<String> task4 = CompletableFuture.supplyAsync(new SupplierTask("task4"));

        CompletableFuture<String> completableFuture = CompletableFuture.allOf(task1, task2, task3, task4).thenApplyAsync(new Function<Void, String>() {
            @Override
            public String apply(Void unused) {
                StupidUtils.showThreadName("thenApplyAsync");
                return "thenApplyAsync";
            }
        }).handle(new BiFunction<String, Throwable, String>() {
            @Override
            public String apply(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "handle");
                return String.format("%s handle", s);
            }
        }).whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "whenCompleteAsync");
            }
        });

        showCompletableFutureResult(completableFuture);
        System.out.println("COMPLETE");
    }

    /**
     * supplyAsync -> anyOf -> thenApplyAsync -> handleAsync
     */
    public void testAnyOf() {
        CompletableFuture<String> task1 = CompletableFuture.supplyAsync(new SupplierTask("task1").setTimeout(800L));
        CompletableFuture<String> task2 = CompletableFuture.supplyAsync(new SupplierTask("task2").setTimeout(1000L));
        CompletableFuture<String> task3 = CompletableFuture.supplyAsync(new SupplierTask("task3").setTimeout(400L));
        CompletableFuture<String> task4 = CompletableFuture.supplyAsync(new SupplierTask("task4").setTimeout(1200L));

        CompletableFuture<String> completableFuture = CompletableFuture.anyOf(task1, task2, task3, task4).thenApplyAsync(new Function<Object, String>() {
            @Override
            public String apply(Object o) {
                StupidUtils.showThreadName((String) o, "thenApply");
                return String.format("%s thenApply", o);
            }
        }).handleAsync(new BiFunction<String, Throwable, String>() {
            @Override
            public String apply(String s, Throwable throwable) {
                StupidUtils.showThreadName(s, "handle");
                return String.format("%s handle", s);
            }
        });

        showCompletableFutureResult(completableFuture);
        System.out.println("COMPLETE");
    }
}
